-- Copyright Epic Games, Inc. All Rights Reserved.

set_configvar("ZEN_SCHEMA_VERSION", 5) -- force state wipe after 0.2.31 causing bad data (dan.engelbrecht)

add_requires(
    "vcpkg::asio",
    "vcpkg::blake3",
    "vcpkg::cpr",
    "vcpkg::curl",
    "vcpkg::cxxopts",
    "vcpkg::doctest",
    "vcpkg::fmt",
    "vcpkg::gsl-lite",
    "vcpkg::http-parser",
    "vcpkg::json11",
    "vcpkg::lua",
    "vcpkg::lz4",
    "vcpkg::ryml",
    "vcpkg::c4core",
    "vcpkg::robin-map",
    "vcpkg::sol2",
    "vcpkg::spdlog",
    "vcpkg::xxhash",
    "vcpkg::zlib"
)

set_policy("build.ccache", false)

if is_plat("linux") then
    add_requires("vcpkg::openssl 3.0.8")
elseif is_plat("windows") then
    add_defines("ZEN_USE_OPENSSL=0")
else 
    add_requires("vcpkg::openssl")
end

if is_plat("windows") then
    -- for bundling, linux tries to compile from source which fails with UE toolchain, fallback is regular zip
    add_requires("7z")
end

if not is_arch("arm64") then
    add_requires("vcpkg::mimalloc")
    if has_config("zensentry") then
        if is_plat("linux") then
            add_requires("vcpkg::sentry-native 0.5.4")
        else
            add_requires("vcpkg::sentry-native")
        end
    end
end

add_rules("mode.debug", "mode.release")
--add_rules("c++.unity_build")

if is_mode("release") then
    set_optimize("smallest")
end

if is_mode("debug") then
    add_defines("DEBUG")
end

if is_mode("debug") then
    add_defines("ZEN_WITH_TESTS=1")
else
    add_defines("ZEN_WITH_TESTS=0")
end

if is_os("windows") then
    add_defines(
        "_CRT_SECURE_NO_WARNINGS",
        "_UNICODE",
        "UNICODE",
        "_CONSOLE",
        "NOMINMAX",             -- stop Windows SDK defining 'min' and 'max'
        "NOGDI",                -- otherwise Windows.h defines 'GetObject'
        "WIN32_LEAN_AND_MEAN",  -- cut down Windows.h
        "_WIN32_WINNT=0x0A00"
    )
    -- Make builds more deterministic and portable
    add_cxxflags("/d1trimfile:$(curdir)\\")     -- eliminates the base path from __FILE__ paths
    add_cxxflags("/experimental:deterministic") -- (more) deterministic compiler output
    add_ldflags("/PDBALTPATH:%_PDB%")           -- deterministic pdb reference in exe

    add_cxxflags("/Zc:preprocessor") -- Enable preprocessor conformance mode    
    -- add_ldflags("/MAP")
end

if is_os("macosx") then
    -- temporary workaround for xcode 12.1 (UE minimum spec is 12.5)
    -- TODO: once CI is updated we should remove this
    add_defines("_LIBCPP_DISABLE_AVAILABILITY")
end

if is_os("linux") or is_os("macosx") then
    add_cxxflags("-Wno-implicit-fallthrough")
    add_cxxflags("-Wno-missing-field-initializers")
    add_cxxflags("-Wno-strict-aliasing")
    add_cxxflags("-Wno-switch")
    add_cxxflags("-Wno-unused-lambda-capture")
    add_cxxflags("-Wno-unused-private-field")
    add_cxxflags("-Wno-unused-value")
    add_cxxflags("-Wno-unused-variable")
end

-- Turn use of undefined cpp macros into errors
if is_os("windows") then
    add_cxxflags("/we4668")
else
    add_cxxflags("-Wundef")
end

function add_define_by_config(define, config_name)
    local value = has_config(config_name) and 1 or 0
    add_defines(define.."="..value)
end

if not is_arch("arm64") then
    option("zensentry")
        set_default(true)
        set_showmenu(true)
        set_description("Enables Sentry support in Zen")
    option_end()
    add_define_by_config("ZEN_USE_SENTRY", "zensentry")

    option("zenmimalloc")
        set_default(true)
        set_showmenu(true)
        set_description("Use MiMalloc as Zen's allocator")
    option_end()
    add_define_by_config("ZEN_USE_MIMALLOC", "zenmimalloc")
end

if is_os("windows") then
    option("httpsys")
        set_default(true)
        set_showmenu(true)
        set_description("Enable http.sys server")
    option_end()
    add_define_by_config("ZEN_WITH_HTTPSYS", "httpsys")
else
    add_defines("ZEN_WITH_HTTPSYS=0")
end

option("zentrace")
    set_default(true)
    set_showmenu(true)
    set_description("Enable UE's Trace support")
option_end()
add_define_by_config("ZEN_WITH_TRACE", "zentrace")

set_warnings("allextra", "error")
set_languages("cxx20")

-- always generate debug information
set_symbols("debug")

includes("src/transports")
includes("src/zenbase")
includes("src/zencore", "src/zencore-test")
includes("src/zenhttp", "src/zenhttp-test")
includes("src/zennet", "src/zennet-test")
includes("src/zenstore", "src/zenstore-test")
includes("src/zenutil", "src/zenutil-test")
includes("src/zenvfs")
includes("src/zenserver", "src/zenserver-test")
includes("src/zen")
includes("src/zentest-appstub")

task("bundle")
    set_menu {
        usage = "xmake bundle",
        description = "Create Zip bundle from binaries",
        options = {
            {nil, "withtrace", "k", nil, "Compiles with trace support"},
            {nil, "codesignidentity", "v", nil, "Code signing identity"},
        }
    }
    on_run(function ()
        import("scripts.bundle")
        bundle()
    end)

task("updatefrontend")
    set_menu {
        usage = "xmake updatefrontend",
        description = "Create Zip of the frontend/html folder for bundling with zenserver executable",
    }
    on_run(function()
        import("scripts.updatefrontend")
        updatefrontend()
    end)

task("precommit")
    set_menu {
        usage = "xmake precommit",
        description = "Run required pre-commit steps (clang-format, etc)",
    }
    on_run(function ()
        print(os.exec("pre-commit run --all-files"))
    end)

task("sln")
    set_menu {
        usage = "xmake sln",
        description = "Generate IDE project files",
    }
    if is_os("windows") then
        on_run(function ()
                print(os.exec("xmake project --yes --kind=vsxmake2022 -m release,debug -a x64"))
        end)
    elseif is_os("macosx") then
        on_run(function ()
                print(os.exec("xmake project --yes --kind=xcode -m release,debug -a x64,arm64"))
        end)
    end

task("test")
    set_menu {
        usage = "xmake test --run=[core|store|server|integration|all]",
        description = "Run Zen tests",
        options = {
            {'r', "run", "kv", "all", "Run test(s)", " - all", " - core ", " - store", " - server", " - integration"},
            {'j', "junit", "k", nil, "Enable junit report output"}
        }
    }
    on_run(function()
        import("core.base.option")
        import("core.project.config")
        import("core.project.project")
        
        config.load()

        local testname = option.get("run")
        local available_tests = {
            core = "zencore-test",
            http = "zenhttp-test",
            util = "zenutil-test",
            store = "zenstore-test",
            server = "zenserver",
            integration = "zenserver-test"
        }

        local arch
        if is_host("windows") then
            arch = "x64"
        elseif is_arch("arm64") then 
            arch = "arm64"
        else
            arch = "x86_64"
        end
        
        print(os.exec("xmake config -c -m debug -a "..arch))
        print(os.exec("xmake"))
        
        local tests = {}
        local found_match = false

        for name, test in pairs(available_tests) do
            if name == testname or testname == "all" then
                tests[name] = test
                found_match = true
            end
        end

        if not found_match then
            raise("no tests match specification: '%s'", testname)
        end

        local use_junit_reporting = option.get("junit")
        local junit_report_files = {}

        local junit_report_dir
        if use_junit_reporting then
            junit_report_dir = path.join(os.projectdir(), config.get("buildir"), "reports")
            os.mkdir(junit_report_dir)
        end

        try
        {
            function()
                for name, test in pairs(tests) do
                    printf("=== %s ===\n", test)
                    local cmd = string.format("xmake run %s", test)
                    if name == "server" then
                        cmd = string.format("xmake run %s test", test)
                    end
                    cmd = string.format("%s --duration=true", cmd)
                    if use_junit_reporting then
                        local target = project.target(test)
                        local junit_report_file = path.join(junit_report_dir, string.format("junit-%s-%s-%s.xml", config.plat(), arch, test))
                        junit_report_files[test] = junit_report_file
                        cmd = string.format("%s --reporters=junit --out=%s", cmd, junit_report_file)
                    end

                    os.exec(cmd)
                end
            end,

            finally
            {
                function (ok, errors)
                    for test, junit_report_file in pairs(junit_report_files) do
                        printf("=== report - %s ===\n", test)
                        local data = io.readfile(junit_report_file)
                        print(data)
                    end
                    if (errors) then
                        raise(errors)
                    end
                end
            }
        }
    end)
